package com.baturu.mybatisminus.injector.parser;

import com.baturu.mybatisminus.model.FieldInfo;
import com.baturu.mybatisminus.model.MethodInfo;
import com.baturu.mybatisminus.base.Sort;
import com.baturu.mybatisminus.model.TableInfo;
import com.baturu.mybatisminus.enums.QueryType;
import com.baturu.mybatisminus.util.CollectionUtils;
import com.baturu.mybatisminus.util.SqlFragments;
import com.baturu.mybatisminus.util.StringUtils;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.util.Assert;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author huangbin
 */
public class PartTreeSqlGenerator {

    private MethodInfo methodInfo;

    private TableInfo tableInfo;

    private PartTree partTree;

    private PartTree.Subject subject;

    private PartTree.Predicate predicate;

    private QueryType queryType;

    private int paramIndex = 1;

    public PartTreeSqlGenerator(MethodInfo methodInfo, TableInfo tableInfo) {
        Assert.notNull(methodInfo, "MethodInfo can not be null!");
        Assert.notNull(tableInfo, "TableInfo can not be null!");
        this.methodInfo = methodInfo;
        this.tableInfo = tableInfo;
        PartTree partTree = new PartTree(methodInfo.getMethodName(), tableInfo);
        this.partTree = partTree;
        this.subject = partTree.getSubject();
        this.predicate = partTree.getPredicate();
        this.queryType = subject.getQueryType();
    }


    public String generateSql() {
        String sql;
        if (queryType.equals(QueryType.SELECT)) {
            sql = generateSelectSql();
        } else if (queryType.equals(QueryType.COUNT)) {
            sql = generateCountSql();
        } else if (queryType.equals(QueryType.DELETE)) {
            sql = generateDeleteSql();
        } else {
            sql = generateUpdateSql();
        }
        return new StringBuilder().append("<script>").append(sql.trim()).append("</script>").toString();
    }

    public SqlCommandType getSqlCommandType() {
        return queryType.getSqlCommandType();
    }


    private String generateSelectSql() {
        String sqlPattern = QueryType.SELECT.getSqlPattern();
        String selectColumn = tableInfo.getSelectColumnString();
        if (CollectionUtils.isNotEmpty(subject.getParts())) {
            Set<String> propertySet = subject.getParts().stream()
                    .map(Part::getProperty).collect(Collectors.toSet());
            selectColumn = tableInfo.getSelectColumnString(propertySet);
        }
        String condition = parsePredicate();
        String sql = String.format(sqlPattern, selectColumn, tableInfo.getEscapedTableName(), condition);
        if (predicate.getOrderBySource() != null) {
            Sort sort = predicate.getOrderBySource().toSort();
            sql += sort.getOrderBy();
        }
        sql = appendSortSql(sql);
        sql = appendPageSql(sql);
        return sql;
    }

    private String generateCountSql() {
        String sqlPattern = QueryType.COUNT.getSqlPattern();
        String selectColumn = "*";
        if (CollectionUtils.isNotEmpty(subject.getParts())) {
            FieldInfo fieldInfo = tableInfo.getFieldInfo(subject.getParts().get(0).getProperty());
            selectColumn = fieldInfo.getEscapedColumn();
        }
        String condition = parsePredicate();
        return String.format(sqlPattern, selectColumn, tableInfo.getEscapedTableName(), condition);
    }

    private String generateDeleteSql() {
        String sqlPattern = QueryType.DELETE.getSqlPattern();
        String condition = parsePredicate();
        return String.format(sqlPattern, tableInfo.getEscapedTableName(), condition);
    }


    private String generateUpdateSql() {
        String sqlPattern = QueryType.UPDATE.getSqlPattern();
        String setParam = parseSetParam();
        String condition = parsePredicate();
        return String.format(sqlPattern, tableInfo.getEscapedTableName(), setParam, condition);
    }


    private String parseSetParam() {
        StringBuilder sb = new StringBuilder();
        for (Part part : subject.getParts()) {
            FieldInfo fieldInfo = part.getFieldInfo();
            String param = SqlFragments.getParam("param" + paramIndex++);
            sb.append(fieldInfo.getEscapedColumn()).append(" = ").append(param).append(", ");
        }
        sb.setLength(sb.length() - 2);
        return sb.toString();
    }


    /**
     * 解析条件部分
     */
    private String parsePredicate() {
        if (predicate.getNodes().size() == 1) {
            return parseOrPart(predicate.getNodes().get(0));
        } else {
            StringBuilder sb = new StringBuilder();
            for (PartTree.OrPart orPart : predicate.getNodes()) {
                if (orPart.getParts().size() > 1) {
                    sb.append("(").append(parseOrPart(orPart)).append(")");
                } else {
                    sb.append(parseOrPart(orPart));
                }
                sb.append(" OR ");
            }
            sb.setLength(sb.length() - 4);
            return sb.toString();
        }

    }

    /**
     * 解析单个OrPart节点
     */
    private String parseOrPart(PartTree.OrPart orPart) {
        int totalRequireParams = partTree.getRequireArguments();
        String onlyOneParamName = methodInfo.getOnlyOneParamName();
        StringBuilder sb = new StringBuilder();
        for (Part part : orPart.getParts()) {
            Part.Type type = part.getType();
            FieldInfo fieldInfo = part.getFieldInfo();
            String sqlPattern = type.getSqlPattern();
            if (part.getRequireArguments() == 0) {
                sb.append(String.format(sqlPattern, fieldInfo.getEscapedColumn()));
            } else if (part.getRequireArguments() == 1) {
                if (totalRequireParams == 1) {
                    onlyOneParamName = StringUtils.isEmpty(onlyOneParamName) ? ("param" + paramIndex++) : onlyOneParamName;
                    sb.append(String.format(sqlPattern, fieldInfo.getEscapedColumn(), onlyOneParamName));
                } else {
                    sb.append(String.format(sqlPattern, fieldInfo.getEscapedColumn(), "param" + paramIndex++));
                }
            } else {
                String firstParam = "param" + paramIndex++;
                String secondParam = "param" + paramIndex++;
                sb.append(String.format(sqlPattern, fieldInfo.getEscapedColumn(), firstParam, secondParam));
            }
            sb.append(" AND ");
        }
        sb.setLength(sb.length() - 5);
        return sb.toString();
    }


    private String appendPageSql(String sql) {
        if (!methodInfo.isPageParamExist()) {
            return sql;
        }
        String pageParamName = StringUtils.isEmpty(methodInfo.getPageParamName()) ? "page" : methodInfo.getPageParamName();
        return sql + SqlFragments.getIfFragment(pageParamName + " != null", "${" + pageParamName + ".limitString}");
    }

    private String appendSortSql(String sql) {
        //没有Sort参数或者已经有OrderBySource则不再排序
        if (!methodInfo.isSortParamExist() || predicate.getOrderBySource() != null) {
            return sql;
        }
        String sortParamName = StringUtils.isEmpty(methodInfo.getSortParamName()) ? "sort" : methodInfo.getSortParamName();
        String condition = sortParamName + " != null and " + sortParamName + ".orders.size()>0";
        String content = "${" + sortParamName + ".orderBy}";
        return sql + SqlFragments.getIfFragment(condition, content);
    }

}
